package org.lc.cmd.plugin.scan;

import lombok.extern.slf4j.Slf4j;
import org.lc.cmd.CmdBuilder;
import org.lc.cmd.CmdParam;
import org.lc.cmd.Command;
import org.lc.cmd.DataCompleter;
import org.lc.cmd.IndexParam;
import org.lc.cmd.OptParam;
import org.lc.cmd.impl.ModelWarp;
import org.lc.cmd.impl.SolonCommand;
import org.lc.cmd.model.Constants;
import org.lc.cmd.plugin.anno.Cmd;
import org.lc.cmd.plugin.anno.CmdField;
import org.lc.cmd.util.AnnoUtil;
import org.lc.cmd.util.CollectionUtil;
import org.lc.cmd.util.ContextUtil;
import org.lc.cmd.util.ConvertUtil;
import org.lc.cmd.util.Lazy;
import org.lc.cmd.util.ReflectUtil;
import org.lc.cmd.util.StringUtil;
import org.springframework.stereotype.Component;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;

@Component
@Slf4j
public class DefaultCmdBuilder implements CmdBuilder {
    protected static final Set<Class<?>> singleTypes = CollectionUtil.newSet(Short.class, Integer.class, Long.class,
            Double.class, Float.class, Boolean.class, Character.class, String.class, Date.class, LocalDate.class,
            LocalTime.class, LocalDateTime.class, BigDecimal.class, BigInteger.class, File.class);

    @Override
    public List<Command> buildCommands(Class<?> clazz) {
        Cmd cmd = AnnoUtil.findAnno(clazz, Cmd.class);
        if (cmd == null) {
            log.warn("class {} is not annotated by @Cmd", clazz);
            return Collections.emptyList();
        }
        if (StringUtil.isEmpty(cmd.value())) {
            log.warn("cmd value is empty, can not build, class: {}", clazz);
            return Collections.emptyList();
        }
        Map<String, SolonCommand.SolonMethodCommand> cmdMap = new HashMap<>();
        for (Method method : clazz.getMethods()) {
            Cmd methodCmd = AnnoUtil.findAnno(method, Cmd.class);
            if (methodCmd == null) {
                continue;
            }
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }
            SolonCommand.SolonMethodCommand subCmd = buildSubCmd(methodCmd, method, clazz);
            cmdMap.put(subCmd.getName(), subCmd);
        }

        if (cmdMap.isEmpty()) {
            log.warn("class {} can not found method with @Cmd annotation", clazz);
            return Collections.emptyList();
        }

        SolonCommand command = new SolonCommand(cmd.value(), null, cmdMap);
        command.setDescription(cmd.description());
        return Collections.singletonList(command);
    }

    protected SolonCommand.SolonMethodCommand buildSubCmd(Cmd methodCmd, Method method, Class<?> clazz) {
        ModelWarp[] models = new ModelWarp[method.getParameterCount()];
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Type genericType = parameter.getParameterizedType();
            Class<?> type = parameter.getType();
            CmdField cmdField = AnnoUtil.findAnno(parameter, CmdField.class);

            ModelWarp model = new ModelWarp();
            String name = cmdField != null && StringUtil.isNotEmpty(cmdField.value()) ? cmdField.value() :
                    parameter.getName();
            boolean withSuperFields = cmdField == null || cmdField.withSuperFields();
            model.setName(name);
            model.setType(type);
            model.setGenericType(genericType);
            model.setCmdField(cmdField);
            Class<?> itemType = getItemType(type, genericType);
            if (itemType != null) {
                model.setCollection(true);
                model.setItemType(itemType);
            }
            if (!isSingleType(type, genericType)) {
                model.setSingle(false);
                if (model.isCollection()) {
                    model.setSubModel(buildSubModels(name, itemType, itemType, true, withSuperFields));
                } else {
                    model.setSubModel(buildSubModels(name, type, genericType, false, withSuperFields));
                }
            }
            models[i] = model;
        }
        List<CmdParam> params = buildParams(models);
        SolonCommand.SolonMethodCommand command = new SolonCommand.SolonMethodCommand(methodCmd.value(), params, null
                , method, models, () -> ContextUtil.getBean(clazz));
        command.setDescription(methodCmd.description());
        return command;
    }

    protected List<CmdParam> buildParams(ModelWarp[] models) {
        List<CmdParam> params = new ArrayList<>();
        Map<String, ModelWarp> sortedModels = new TreeMap<>();
        Map<String, OptParam> longOptParams = new HashMap<>();
        Map<Character, OptParam> shortOptParams = new HashMap<>();
        List<IndexParam> indexParams = new ArrayList<>();
        List<ModelWarp> secondHandleModels = new ArrayList<>();
        for (ModelWarp model : models) {
            flatModel("", model, sortedModels);
        }
        // 首轮构造：通过注解指定名称/类型的参数
        for (Map.Entry<String, ModelWarp> entry : sortedModels.entrySet()) {
            ModelWarp model = entry.getValue();
            boolean handled = false;
            CmdField cmdField = model.getCmdField();
            if (cmdField != null) {
                if (cmdField.paramType() == CmdField.Type.INDEX
                        || (cmdField.paramType() == CmdField.Type.AUTO && cmdField.required())) {
                    handled = true;
                    IndexParam indexParam = new IndexParam(indexParams.size());
                    setCommonParam(indexParam, model);
                    indexParams.add(indexParam);
                    params.add(indexParam);
                } else {
                    OptParam param = new OptParam();
                    setCommonParam(param, model);
                    if (CollectionUtil.isNotEmpty(cmdField.shortOpts())) {
                        handled = true;
                        param.setShortOpts(CollectionUtil.toList(cmdField.shortOpts()));
                        param.getShortOpts().forEach(opt -> shortOptParams.put(opt, param));
                    }
                    if (CollectionUtil.isNotEmpty(cmdField.longOpts())) {
                        handled = true;
                        param.setLongOpts(Arrays.asList(cmdField.longOpts()));
                        param.getLongOpts().forEach(opt -> longOptParams.put(opt, param));
                    }
                    if (handled) {
                        params.add(param);
                    }
                }
            }
            if (!handled) {
                secondHandleModels.add(model);
            }
        }
        // 第二轮构造不带注解的参数
        for (ModelWarp model : secondHandleModels) {
            String fullName = model.getName();
            String lastName = fullName.substring(fullName.lastIndexOf('/') + 1);
            char ch = lastName.charAt(0);
            char upperCase = Character.toUpperCase(ch);
            char lowerCase = Character.toLowerCase(ch);
            OptParam param = new OptParam();
            setCommonParam(param, model);
            if (!shortOptParams.containsKey(lowerCase)) {
                param.setShortOpts(Collections.singletonList(lowerCase));
                shortOptParams.put(lowerCase, param);
            } else if (!shortOptParams.containsKey(upperCase)) {
                param.setShortOpts(Collections.singletonList(upperCase));
                shortOptParams.put(upperCase, param);
            }
            if (!longOptParams.containsKey(lastName)) {
                param.setLongOpts(Collections.singletonList(lastName));
                longOptParams.put(lastName, param);
            } else {
                String longOpt = fullName.replaceAll("/", "-");
                param.setLongOpts(Collections.singletonList(longOpt));
                longOptParams.put(longOpt, param);
            }
            params.add(param);
        }

        return params;
    }

    protected void setCommonParam(CmdParam cmdParam, ModelWarp model) {
        cmdParam.setName(model.getName());
        CmdField cmdField = model.getCmdField();
        if (cmdField != null) {
            cmdParam.setDescription(cmdField.description());
            cmdParam.setRequired(cmdField.required());
            if (!cmdField.defaultValue().equals(Constants.PARM_UNDEFINED_VALUE)) {
                cmdParam.setDefaultValue(ConvertUtil.to(model.getType(), model.getGenericType(),
                        cmdField.defaultValue()));
            }
            if (!cmdField.completer().equals(DataCompleter.class)) {
                cmdParam.setCompleter(new Lazy<>(() -> ContextUtil.getBean(cmdField.completer())));
            }
        }
        cmdParam.setType(model.getType());
        cmdParam.setGenericType(model.getGenericType());
        cmdParam.setMultiple(model.isMultiple());
        if (model.isSingle() && model.isCollection()) {
            cmdParam.setMultiple(true);
        }
        if (cmdParam instanceof OptParam) {
            OptParam optParam = (OptParam) cmdParam;
            if (Boolean.class.equals(model.getType()) || boolean.class.equals(model.getType())) {
                optParam.setWithParamData(false);
            }
        }
    }

    protected void flatModel(String name, ModelWarp model, Map<String, ModelWarp> models) {
        String nowName = name + "/" + model.getName();
        if (model.isSingle()) {
            models.put(nowName, model);
        }
        if (CollectionUtil.isNotEmpty(model.getSubModel())) {
            for (ModelWarp modelWarp : model.getSubModel()) {
                flatModel(nowName, modelWarp, models);
            }
        }
    }

    protected List<ModelWarp> buildSubModels(String parentName, Class<?> type, Type genericType, boolean multiple,
            boolean withSuperFields) {
        @SuppressWarnings("rawtypes")
        Class<?> parentType = Optional.ofNullable((Class) getItemType(type, genericType)).orElse(type);
        List<ModelWarp> models = new ArrayList<>();
        for (Field field : ReflectUtil.getAllFields(parentType, withSuperFields)) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            CmdField cmdField = AnnoUtil.findAnno(field, CmdField.class);
            ModelWarp model = new ModelWarp();
            String name = parentName + "/" + (cmdField != null && StringUtil.isNotEmpty(cmdField.value()) ?
                    cmdField.value() : field.getName());
            boolean nextWithSuperFields = cmdField == null || cmdField.withSuperFields();
            model.setName(name);
            model.setMultiple(multiple);
            model.setType(field.getType());
            model.setGenericType(field.getGenericType());
            model.setField(field);
            model.setCmdField(cmdField);
            Class<?> itemType = getItemType(model.getType(), model.getGenericType());
            if (itemType != null) {
                model.setCollection(true);
                model.setItemType(itemType);
            }
            if (!isSingleType(model.getType(), model.getGenericType())) {
                model.setSingle(false);
                if (model.isCollection()) {
                    model.setSubModel(buildSubModels(name, itemType, itemType, true, nextWithSuperFields));
                } else {
                    model.setSubModel(buildSubModels(name, model.getType(), model.getGenericType(), multiple,
                            nextWithSuperFields));
                }
            }
            models.add(model);
        }
        return models;
    }

    protected boolean isSingleType(Class<?> type, Type genericType) {
        if (type.isArray()) {
            return isSingleType(type.getComponentType(), null);
        }
        if (Collection.class.isAssignableFrom(type)) {
            if (genericType instanceof ParameterizedType) {
                Type typeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
                if (typeArgument instanceof Class) {
                    return isSingleType((Class<?>) typeArgument, typeArgument);
                }
                log.warn("Can not get genericType info of Collection, type: {}", genericType);
                return true;
            }
            return true;
        }
        // 后续考虑支持map
        if (type.isPrimitive()) {
            return true;
        }
        if (singleTypes.contains(type)) {
            return true;
        }
        return type.isEnum();
    }

    protected Class<?> getItemType(Class<?> type, Type genericType) {
        if (type.isArray()) {
            return type.getComponentType();
        }
        if (Collection.class.isAssignableFrom(type)) {
            if (genericType instanceof ParameterizedType) {
                Type typeArgument = ((ParameterizedType) genericType).getActualTypeArguments()[0];
                if (typeArgument instanceof Class) {
                    return (Class<?>) typeArgument;
                }
            }
            // 如果找不到集合的泛型类型，直接往集合塞字符串
            return String.class;
        }
        return null;
    }

}
